package org.evej.apiparser.parser.handler;

import org.evej.apiparser.model.ApiDocument;
import org.evej.apiparser.model.ApiDocumentCacheInfo;
import org.evej.apiparser.model.ApiDocumentModel;
import org.evej.apiparser.model.ApiError;

import org.evej.apiparser.parser.errorhandling.ApiHandlerExcption;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Base document SAX handler for all handlers. Handles common error handling and caching elements.
 */
public abstract class ApiDocumentHandler<T extends ApiDocumentModel> extends DefaultHandler {

    private static final SimpleDateFormat DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    protected int version;
    protected ApiDocument<T> document;
    protected ApiDocumentCacheInfo.Builder documentCacheInfoBuilder = new ApiDocumentCacheInfo.Builder();
    protected T documentModel;

    protected List<String> currentPath = new ArrayList<>();
    protected StringBuilder elementContent = new StringBuilder(50);
    protected ApiError apiError;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
        if (qName.equals("eveapi")) {
            version = attributeToInt(attrs, "version");
        } else if (qName.equals("error")) {
            apiError = new ApiError();
            apiError.setErrorCode(attributeToInt(attrs, "code"));
        }
        elementContent.setLength(0);
        currentPath.add(qName);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        switch (qName) {
            case "currentTime":
                documentCacheInfoBuilder.setRetrievalTime(getDate());
                break;
            case "cachedUntil":
                documentCacheInfoBuilder.setCachedUntil(getDate());
                break;
            case "error":
                apiError.setErrorMessage(elementContentToString());
                break;
        }
        elementContent.setLength(0);
        currentPath.remove(currentPath.size() - 1);
    }

    @Override
    public void endDocument() throws SAXException {
        document = new ApiDocument<T>(documentCacheInfoBuilder.build(), documentModel, version);
    }

    @Override
    public final void characters(char[] ch, int start, int length) throws SAXException {
        elementContent.append(ch, start, length);
    }

    protected final String elementContentToString() {
        return elementContent.toString().trim();
    }

    protected final String attributeToString(Attributes attrs, String qName) {
        return attrs.getValue(qName);
    }

    protected final Date getDate() {
        try {
            return DATEFORMAT.parse(elementContentToString());
        } catch (ParseException e) {
            throw new ApiHandlerExcption("Unable to parse element at " + getCurrentElementPathAsString() + " to date " + elementContentToString());
        }
    }

    protected final Date getDate(Attributes attrs, String qName) {
        try {
            return DATEFORMAT.parse(attrs.getValue(qName));
        } catch (ParseException e) {
            throw new ApiHandlerExcption("Unable to parse attribute " + qName + " at  " + getCurrentElementPathAsString() + " to date " + attrs.getValue(qName));
        }
    }

    protected Integer elementContentToInt() {
        try {
            return Integer.parseInt(elementContentToString());
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse element at " + getCurrentElementPathAsString() + " to int " + elementContentToString());
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value at " + getCurrentElementPathAsString() + " cannot be parsed to int");
        }
    }

    protected Integer attributeToInt(Attributes attrs, String qName) {
        try {
            return Integer.parseInt(attrs.getValue(qName));
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse attribute " + qName + " at " + getCurrentElementPathAsString() + " to int " + attrs.getValue(qName));
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value of attribute " + qName + "at  " + getCurrentElementPathAsString() + " cannot be parsed to int ");
        }
    }

    protected Long elementContentToLong() {
        try {
            return Long.parseLong(elementContentToString());
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse element at  " + getCurrentElementPathAsString() + " to long " + elementContentToString());
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value at  " + getCurrentElementPathAsString() + " cannot be parsed to int");
        }
    }

    protected Long attributeToLong(Attributes attrs, String qName) {
        try {
            return Long.parseLong(attrs.getValue(qName));
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse attribute " + qName + " at  " + getCurrentElementPathAsString() + " to long " + attrs.getValue(qName));
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value of attribute " + qName + "at " + getCurrentElementPathAsString() + " cannot be parsed to long ");
        }
    }

    protected Double elementContentToDouble() {
        try {
            return Double.parseDouble(elementContentToString());
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse element at path " + getCurrentElementPathAsString() + " to double " + elementContentToString());
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value at  " + getCurrentElementPathAsString() + " cannot be parsed to double");
        }
    }

    protected Double attributeToDouble(Attributes attrs, String qName) {
        try {
            return Double.parseDouble(attrs.getValue(qName));
        } catch (NumberFormatException e) {
            throw new ApiHandlerExcption("Unable to parse attribute " + qName + " at " + getCurrentElementPathAsString() + " to double " + attrs.getValue(qName));
        } catch (NullPointerException e) {
            throw new ApiHandlerExcption("Null value of attribute " + qName + "at " + getCurrentElementPathAsString() + " cannot be parsed to double");
        }
    }

    protected boolean elementContentToBoolean() {
        return elementContentToString().equals("1") || "true".equalsIgnoreCase(elementContentToString());
    }

    protected boolean attributeToBoolean(Attributes attrs, String qName) {
        return "1".equals(attrs.getValue(qName)) || "true".equalsIgnoreCase(attrs.getValue(qName));
    }

    protected String getCurrentElementPathAsString() {
        StringBuilder elementPath = new StringBuilder(200);
        for (int i = 0; i < currentPath.size(); i++) {
            elementPath.append("<" + currentPath.get(i) + ">");
        }
        return elementPath.toString();
    }

    public ApiDocument<T> getApiDocument() {
        return document;
    }

    public ApiError getError() {
        return apiError;
    }

}
